En esta página mostraremos cómo crear una interfaz gráfica sencilla con Qt Designer, la herramienta oficial de Qt para el diseño de interfaces. El diálogo se convertirá a código Python y luego se utilizará en FreeCAD. Supondremos que el usuario sabe editar y ejecutar código Python en general.
En este ejemplo, toda la interfaz está definida en Python. Si bien esto es posible para interfaces pequeñas, para interfaces más grandes se recomienda cargar los archivos .ui creados directamente en el programa.
Dos métodos generales para crear interfaces: incluyendo la interfaz en el archivo Python o utilizando archivos .ui.
En las aplicaciones CAD, diseñar una buena interfaz de usuario (UI) es fundamental. Prácticamente, todas las acciones del usuario se realizarán a través de algún elemento de la interfaz: leer cuadros de diálogo, pulsar botones, seleccionar iconos, etc. Por lo tanto, es crucial reflexionar detenidamente sobre qué se desea lograr, cómo se espera que se comporte el usuario y cuál será el flujo de trabajo de la acción.
Hay algunos conceptos clave para diseñar interfaces:
Ahora que ya hemos definido bien lo que se pretende hacer, es hora de abrir el diseñador de Qt. Vamos a diseñar un diálogo tan sencillo como este:
Luego usaremos este cuadro de diálogo en FreeCAD para crear un plano rectangular bien definido. Quizás no le resulte muy útil para crear planos rectangulares, pero será fácil modificarlo más adelante para realizar tareas más complejas. Al abrirlo, Qt Designer se ve así:
Qt Designer es muy sencillo de usar. En la barra lateral izquierda, encontrará elementos que puede arrastrar al widget. En la derecha, verá paneles de propiedades que muestran las propiedades editables de los elementos seleccionados. Comencemos creando un nuevo widget.
Nota: Aquí hemos optado por controles muy sencillos. Qt ofrece muchas más opciones; por ejemplo, se podrían usar Spinboxes en lugar de LineEdits, etc. Échele un vistazo a las opciones disponibles, explore... seguro que se le ocurrirán otras opciones.
Eso es prácticamente todo lo que necesitamos hacer en Qt Designer. Sin embargo, una última cosa: vamos a renombrar todos nuestros elementos con nombres más sencillos para que sea más fácil identificarlos en nuestros scripts.
Ahora, guardemos nuestro widget en algún lugar. Se guardará como un archivo .ui, que convertiremos fácilmente a un script de Python con pyuic. En Windows, el programa pyuic viene incluido con PyQt (esto se puede verificar); en Linux, probablemente tendrá que instalarlo por separado desde tu gestor de paquetes (en sistemas basados en Debian, forma parte del paquete pyqt4-dev-tools). Para realizar la conversión, tendrá que abrir una ventana desde una terminal (o una ventana de símbolo del sistema en Windows) y navegar hasta donde guardó su archivo .ui para poder ejecutarlo:
pyuic mywidget.ui > mywidget.py
En Windows, pyuic.py se encuentra en C:\Python27\Lib\site-packages\PyQt4\uic\pyuic.py. Para la conversión, cree un archivo por lotes llamado compQt4.bat:
@"C:\Python27\python" "C:\Python27\Lib\site-packages\PyQt4\uic\pyuic.py" -x %1.ui > %1.py
En la consola de DOS escriba sin extensión:
compQt4 myUiFile
En macOS, puede obtener la versión adecuada (la misma que se usa internamente en FreeCAD 0.19) de QT y Pyside con estos comandos (se requiere pip).
python3 -m pip install pyqt5
python3 -m pip install pySide2
Esto instalará uic en la carpeta /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/PySide2/uic y Designer en /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/PySide2/Designer.app.
Para mayor comodidad, puede crear un enlace simbólico de uic en /usr/local/bin para poder ejecutarlo simplemente con uic -g python ... en lugar de escribir la ruta completa del programa, y un enlace simbólico de Designer para recuperarlo en la carpeta Aplicaciones de su Mac.
sudo ln -s /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/PySide2/uic /usr/local/bin
ln -s /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/PySide2/Designer.app /Applications
En Linux hacer:
Dado que FreeCAD se fue alejando progresivamente de PyQt después de la versión 0.13, a favor de PySide (Elija su instalación de PySide build PySide). Para hacer que el archivo se base en PySide ahora debe usar:
pyside-uic mywidget.ui -o mywidget.py
En Windows, el archivo uic.py se encuentra en C:\Python27\Lib\site-packages\PySide\scripts\uic.py. Para crear el archivo por lotes compSide.bat:
@"C:\Python27\python" "C:\Python27\Lib\site-packages\PySide\scripts\uic.py" %1.ui > %1.py
En la consola de DOS escriba sin extensión:
compSide myUiFile
En Linux hacer:
En algunos sistemas, el programa se llama pyuic4 en lugar de pyuic. Esto simplemente convertirá el archivo .ui en un script de Python. Si abrimos el archivo mywidget.py, su contenido es muy fácil de entender:
from PySide import QtCore, QtGui
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(187, 178)
self.title = QtGui.QLabel(Dialog)
self.title.setGeometry(QtCore.QRect(10, 10, 271, 16))
self.title.setObjectName("title")
self.label_width = QtGui.QLabel(Dialog)
...
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8))
self.title.setText(QtGui.QApplication.translate("Dialog", "Plane-O-Matic", None, QtGui.QApplication.UnicodeUTF8))
...
Como puede ver, tiene una estructura muy simple: se crea una clase llamada Ui_Dialog que almacena los elementos de la interfaz de nuestro widget. Esta clase tiene dos métodos: uno para configurar el widget y otro para traducir su contenido, lo cual forma parte del mecanismo general de Qt para la traducción de elementos de interfaz. El método de configuración simplemente crea, uno por uno, los widgets tal como los definimos en Qt Designer y establece sus opciones según lo acordado previamente. Luego, se traduce toda la interfaz y, finalmente, se conectan las conexiones (slots) (lo veremos más adelante).
Ahora podemos crear un nuevo widget y usar esta clase para crear su interfaz. Ya podemos ver nuestro widget en acción colocando nuestro archivo mywidget.py en un lugar donde FreeCAD lo encontrará (en el directorio bin de FreeCAD o en cualquiera de los subdirectorios Mod) y, en el intérprete de Python de FreeCAD, ejecutando:
from PySide import QtGui
import mywidget
d = QtGui.QWidget()
d.ui = mywidget.Ui_Dialog()
d.ui.setupUi(d)
d.show()
¡Y nuestro letrero de diálogo aparecerá! Tenga en cuenta que nuestro intérprete de Python todavía está trabajando, ya que hemos usado un letrero de diálogo no modal. Por lo tanto, para cerrarlo, podemos (aparte de hacer clic en el icono de cerrar, por supuesto) escribir:
d.hide()
== Haciendo que nuestro diálogo funcione == Ahora que podemos mostrar y ocultar nuestro diálogo, solo necesitamos añadir una última parte: ¡Hacer que funcione! Si experimenta un poco con Qt Designer, descubrirá rápidamente una sección completa llamada señales y ranuras. Básicamente, funciona así: los elementos de sus widgets (en la terminología de Qt, estos elementos son widgets) pueden enviar señales. Estas señales varían según el tipo de widget. Por ejemplo, un botón puede enviar una señal al pulsarlo y al soltarlo. Estas señales se pueden conectar a ranuras, que pueden ser funcionalidades especiales de otros widgets (por ejemplo, un diálogo tiene una ranura cerrar a la que puede conectar la señal de un botón de cierre), o pueden ser funciones personalizadas. La Documentación de referencia de PyQt enumera todos los widgets de Qt, sus funciones, las señales que pueden enviar, etc.
Lo que haremos aquí será diseñar una nueva función que creará un plano en función de su altura y anchura, y conectar esa función a la señal pulsada emitida por nuestro botón ¡Crear!. Así pues, comencemos importando nuestros módulos de FreeCAD, colocando la siguiente línea al principio del script, donde ya importamos QtCore y QtGui:
import FreeCAD, Part
A continuación, agreguemos una nueva función a nuestra clase Ui_Dialog:
def createPlane(self):
try:
# first we check if valid numbers have been entered
w = float(self.width.text())
h = float(self.height.text())
except ValueError:
print("Error! Width and Height values must be valid numbers!")
else:
# create a face from 4 points
p1 = FreeCAD.Vector(0,0,0)
p2 = FreeCAD.Vector(w,0,0)
p3 = FreeCAD.Vector(w,h,0)
p4 = FreeCAD.Vector(0,h,0)
pointslist = [p1,p2,p3,p4,p1]
mywire = Part.makePolygon(pointslist)
myface = Part.Face(mywire)
Part.show(myface)
self.hide()
Luego, debemos indicarle a Qt que conecte el botón a la función, colocando la siguiente línea justo antes de QtCore.QMetaObject.connectSlotsByName(Dialog):
QtCore.QObject.connect(self.create,QtCore.SIGNAL("pressed()"),self.createPlane)
Como puede ver, esto conecta la señal pressed() de nuestro objeto create (el botón ¡Crear!) a una ranura llamada createPlane, que acabamos de definir. ¡Eso es todo! Ahora, como toque final, podemos agregar una pequeña función para crear el diálogo; será más fácil llamarla. Fuera de la clase Ui_Dialog, agreguemos el siguiente código:
class plane():
def __init__(self):
self.d = QtGui.QWidget()
self.ui = Ui_Dialog()
self.ui.setupUi(self.d)
self.d.show()
(Recordatorio de Python: ¡the __init__ method de una clase se ejecuta automáticamente cada vez que se crea un nuevo objeto!) Luego, desde FreeCAD, solo necesitamos hacer lo siguiente:
import mywidget
myDialog = mywidget.plane()
Eso es todo, amigos... Ahora pueden probar todo tipo de cosas, como por ejemplo, insertar su widget en la interfaz de FreeCAD (consulte la página Fragmentos de código), o crear herramientas personalizadas mucho más avanzadas, utilizando otros elementos en su widget.
Este es el guion completo, para referencia:
# Form implementation generated from reading ui file 'mywidget.ui'
#
# Created: Mon Jun 1 19:09:10 2009
# by: PyQt4 UI code generator 4.4.4
# Modified for PySide 16:02:2015
# WARNING! All changes made in this file will be lost!
from PySide import QtCore, QtGui
import FreeCAD, Part
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(187, 178)
self.title = QtGui.QLabel(Dialog)
self.title.setGeometry(QtCore.QRect(10, 10, 271, 16))
self.title.setObjectName("title")
self.label_width = QtGui.QLabel(Dialog)
self.label_width.setGeometry(QtCore.QRect(10, 50, 57, 16))
self.label_width.setObjectName("label_width")
self.label_height = QtGui.QLabel(Dialog)
self.label_height.setGeometry(QtCore.QRect(10, 90, 57, 16))
self.label_height.setObjectName("label_height")
self.width = QtGui.QLineEdit(Dialog)
self.width.setGeometry(QtCore.QRect(60, 40, 111, 26))
self.width.setObjectName("width")
self.height = QtGui.QLineEdit(Dialog)
self.height.setGeometry(QtCore.QRect(60, 80, 111, 26))
self.height.setObjectName("height")
self.create = QtGui.QPushButton(Dialog)
self.create.setGeometry(QtCore.QRect(50, 140, 83, 26))
self.create.setObjectName("create")
self.retranslateUi(Dialog)
QtCore.QObject.connect(self.create,QtCore.SIGNAL("pressed()"),self.createPlane)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
Dialog.setWindowTitle("Dialog")
self.title.setText("Plane-O-Matic")
self.label_width.setText("Width")
self.label_height.setText("Height")
self.create.setText("Create!")
print("tyty")
def createPlane(self):
try:
# first we check if valid numbers have been entered
w = float(self.width.text())
h = float(self.height.text())
except ValueError:
print("Error! Width and Height values must be valid numbers!")
else:
# create a face from 4 points
p1 = FreeCAD.Vector(0,0,0)
p2 = FreeCAD.Vector(w,0,0)
p3 = FreeCAD.Vector(w,h,0)
p4 = FreeCAD.Vector(0,h,0)
pointslist = [p1,p2,p3,p4,p1]
mywire = Part.makePolygon(pointslist)
myface = Part.Face(mywire)
Part.show(myface)
class plane():
def __init__(self):
self.d = QtGui.QWidget()
self.ui = Ui_Dialog()
self.ui.setupUi(self.d)
self.d.show()
QPushButton, QLineEdit, QCheckBox, QRadioButton, y otros.QFileDialog.QColorDialog.QLabel y QMovie.